#inc/admin_backup.py
import os
import subprocess
import shutil
from datetime import datetime
from flask import current_app, g
from datetime import datetime, timedelta
from inc.config import config
from inc.models import db, DatabaseConfig, BackupLog
from inc.config_logger import log_backup
from inc.config_manager import SystemConfigManager
import pytz

class BackupManager:
    def __init__(self, app=None):
        self.app = app
        if app:
            self.backup_dir = app.config.get('BACKUP_DIR', 'backups')
            os.makedirs(self.backup_dir, exist_ok=True)
        else:
            self.backup_dir = None

    def _get_app(self):
        """获取应用实例，优先使用传入的 app，其次使用 current_app"""
        if self.app:
            return self.app
        try:
            return current_app._get_current_object()
        except RuntimeError:
            # 如果没有应用上下文，创建一个简单的应用对象
            from flask import Flask
            app = Flask(__name__)
            app.config['BACKUP_DIR'] = 'backups'
            return app

    def _get_backup_dir(self):
        """获取备份目录"""
        app = self._get_app()
        if self.backup_dir is None:
            self.backup_dir = app.config.get('BACKUP_DIR', 'backups')
            os.makedirs(self.backup_dir, exist_ok=True)
        return self.backup_dir

    def get_db_config(self):
        """获取当前激活的数据库配置"""
        app = self._get_app()

        # 首先尝试从 DatabaseConfig 表获取配置
        db_config = DatabaseConfig.query.filter_by(is_active=True).first()

        if db_config:
            if db_config.db_type.lower() == 'sqlite' and not os.path.isabs(db_config.database):
                # 获取项目根目录
                base_dir = os.path.dirname(os.path.abspath(__file__))  # 获取当前文件所在目录
                project_root = os.path.abspath(os.path.join(base_dir, '../../..'))  # 向上三级到项目根目录

                # 转换为绝对路径，确保指向 database 目录
                db_path = os.path.join('database', os.path.basename(db_config.database))
                db_config.database = os.path.abspath(os.path.join(project_root, db_path))
            return db_config


        # 如果没有找到 DatabaseConfig 记录，则从应用配置中获取
        from sqlalchemy.engine.url import make_url
        db_uri = app.config['SQLALCHEMY_DATABASE_URI']
        url = make_url(db_uri)

        # 创建一个简单的配置对象
        class SimpleDBConfig:
            def __init__(self, url, app):
                if url.drivername == 'sqlite':
                    self.db_type = 'sqlite'
                    # 确保 SQLite 数据库路径是绝对路径
                    db_path = url.database
                    if not os.path.isabs(db_path):
                        base_dir = app.config.get('BASE_DIR')
                        if base_dir:
                            db_path = os.path.abspath(os.path.join(base_dir, '..', db_path))
                    self.database = db_path
                    self.host = None
                    self.port = None
                    self.username = None
                    self.password = None
                    self.is_active = True
                elif url.drivername.startswith('mysql'):
                    self.db_type = 'mysql'
                    self.database = url.database
                    self.host = url.host
                    self.port = url.port or 3306
                    self.username = url.username
                    self.password = url.password
                    self.is_active = True
                else:
                    raise ValueError(f"不支持的数据库类型: {url.drivername}")

        return SimpleDBConfig(url, app)

    def perform_backup(self, operator_id=None, backup_type="manual"):
        """执行数据库备份"""
        try:
            db_config = self.get_db_config()
            if not db_config:
                return False, "没有可用的数据库配置"

            # 获取时区
            try:
                tz = pytz.timezone(g.system_config.timezone if hasattr(g, 'system_config') else 'Asia/Shanghai')
            except RuntimeError:  # 没有应用上下文时
                tz = pytz.timezone('Asia/Shanghai')

            # 生成时间戳用于文件名（本地时间）
            timestamp = datetime.now(tz).strftime('%Y%m%d_%H%M%S')
            # 生成数据库记录时间（本地时间）
            backup_time_local = datetime.now()

            backup_dir = self._get_backup_dir()

            if db_config.db_type == 'mysql':
                # MySQL备份
                filename = f"backup_{timestamp}.sql"
                backup_path = os.path.join(backup_dir, filename)

                # 构建 mysqldump 命令
                cmd = [
                    'mysqldump',
                    f"--host={db_config.host}",
                    f"--port={db_config.port}",
                    f"--user={db_config.username}",
                    f"--password={db_config.password}",
                    "--single-transaction",
                    "--routines",
                    "--triggers",
                    db_config.database
                ]

                with open(backup_path, 'w') as f:
                    result = subprocess.run(cmd, stdout=f, stderr=subprocess.PIPE, text=True)

            else:  # SQLite
                # SQLite备份 - 直接复制文件
                filename = f"backup_{timestamp}.db"
                backup_path = os.path.join(backup_dir, filename)

                if not os.path.exists(db_config.database):
                    return False, f"数据库文件不存在: {db_config.database}"

                shutil.copy2(db_config.database, backup_path)
                result = subprocess.CompletedProcess(args=[], returncode=0)

            if result.returncode == 0:
                # 记录备份日志 - 明确设置时间
                file_size = os.path.getsize(backup_path)
                app = self._get_app()
                with app.app_context():
                    # 直接创建 BackupLog 对象，明确设置时间
                    backup_log = BackupLog(
                        backup_type=backup_type,
                        filename=filename,
                        file_size=file_size,
                        operator_id=operator_id,
                        status="success",
                        backup_time=backup_time_local  # 明确设置本地时间
                    )
                    db.session.add(backup_log)
                    db.session.commit()

                # 清理旧备份
                self.cleanup_old_backups()

                return True, f"备份成功: {filename} ({self.format_file_size(file_size)})"
            else:
                error_msg = result.stderr if result.stderr else "未知错误"
                app = self._get_app()
                with app.app_context():
                    # 错误时也明确设置时间
                    backup_log = BackupLog(
                        backup_type=backup_type,
                        filename=filename,
                        file_size=0,
                        operator_id=operator_id,
                        status="failure",
                        backup_time=backup_time_local
                    )
                    db.session.add(backup_log)
                    db.session.commit()
                return False, f"备份失败: {error_msg}"

        except Exception as e:
            # 异常处理也要记录日志
            app = self._get_app()
            with app.app_context():
                try:
                    backup_log = BackupLog(
                        backup_type=backup_type,
                        filename=f"backup_error_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
                        file_size=0,
                        operator_id=operator_id,
                        status="failure",
                        backup_time=datetime.now()  # 使用当前的本地时间
                    )
                    db.session.add(backup_log)
                    db.session.commit()
                except Exception as log_error:
                    print(f"记录备份异常日志失败: {str(log_error)}")

            return False, f"备份异常: {str(e)}"


    def perform_restore(self, backup_file, operator_id):
        """执行数据库恢复"""
        try:
            db_config = self.get_db_config()
            if not db_config:
                return False, "没有激活的数据库配置"

            backup_dir = self._get_backup_dir()
            backup_path = os.path.join(backup_dir, backup_file)
            if not os.path.exists(backup_path):
                return False, f"备份文件不存在: {backup_file}"

                # 生成数据库记录时间（本地时间）
            restore_time_local = datetime.now()


            if db_config.db_type == 'mysql':
                # MySQL恢复
                cmd = [
                    'mysql',
                    f"--host={db_config.host}",
                    f"--port={db_config.port}",
                    f"--user={db_config.username}",
                    f"--password={db_config.password}",
                    db_config.database
                ]

                with open(backup_path, 'r') as f:
                    result = subprocess.run(cmd, stdin=f, stderr=subprocess.PIPE, text=True)

            else:  # SQLite
                # SQLite恢复 - 替换数据库文件
                if os.path.exists(db_config.database):
                    # 备份当前数据库
                    current_timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
                    current_backup = f"pre_restore_{current_timestamp}.db"
                    shutil.copy2(db_config.database, os.path.join(backup_dir, current_backup))

                # 替换数据库文件
                shutil.copy2(backup_path, db_config.database)
                result = subprocess.CompletedProcess(args=[], returncode=0)

            if result.returncode == 0:
                # 记录恢复日志
                app = self._get_app()
                with app.app_context():
                    backup_log = BackupLog(
                        backup_type="restore",
                        filename=backup_file,
                        file_size=os.path.getsize(backup_path),
                        operator_id=operator_id,
                        status="success",
                        backup_time=restore_time_local  # 明确设置本地时间
                    )
                return True, "数据库恢复成功"
            else:
                error_msg = result.stderr if result.stderr else "未知错误"
                app = self._get_app()
                with app.app_context():
                    backup_log = BackupLog(
                        backup_type="restore",
                        filename=backup_file,
                        file_size=os.path.getsize(backup_path),
                        operator_id=operator_id,
                        status="failure",
                        backup_time=restore_time_local
                    )
                    db.session.add(backup_log)
                    db.session.commit()
                return False, f"恢复失败: {error_msg}"

        except Exception as e:
            app = self._get_app()
            app.logger.error(f"恢复异常: {str(e)}")
            return False, f"恢复异常: {str(e)}"

    def list_backups(self):
        """列出所有备份文件"""
        backups = []
        backup_dir = self._get_backup_dir()
        try:
            if not os.path.exists(backup_dir):
                os.makedirs(backup_dir, exist_ok=True)
                return backups

            for filename in os.listdir(backup_dir):
                if filename.startswith('backup_') and (filename.endswith('.sql') or filename.endswith('.db')):
                    filepath = os.path.join(backup_dir, filename)
                    try:
                        stat = os.stat(filepath)
                        backups.append({
                            'filename': filename,
                            'size': stat.st_size,
                        'mtime': datetime.fromtimestamp(stat.st_mtime,
                                pytz.timezone(g.system_config.timezone if hasattr(g, 'system_config') else 'Asia/Shanghai')
                        ).strftime('%Y-%m-%d %H:%M:%S'),
                            'formatted_size': self.format_file_size(stat.st_size)
                        })
                    except Exception as e:
                        current_app.logger.warning(f"获取文件信息失败 {filename}: {str(e)}")
                        continue

            # 按修改时间倒序排序
            backups.sort(key=lambda x: x['mtime'], reverse=True)
            return backups
        except Exception as e:
            current_app.logger.error(f"获取备份列表失败: {str(e)}")
            return []

    def cleanup_old_backups(self):
        """清理旧的备份文件，只保留指定数量的备份"""
        # 使用SystemConfigManager获取配置
        retention_count = SystemConfigManager.get_value('backup_retention_count', 30)

        backups = self.list_backups()
        backup_dir = self._get_backup_dir()

        if len(backups) > retention_count:
            # 删除最旧的备份
            for backup in backups[retention_count:]:
                try:
                    os.remove(os.path.join(backup_dir, backup['filename']))
                    current_app.logger.info(f"删除旧备份: {backup['filename']}")
                except Exception as e:
                    current_app.logger.error(f"删除备份失败 {backup['filename']}: {str(e)}")


    def format_file_size(self, size_bytes):
        """格式化文件大小显示"""
        if size_bytes == 0:
            return "0B"

        size_names = ["B", "KB", "MB", "GB"]
        i = 0
        size = size_bytes

        while size >= 1024 and i < len(size_names) - 1:
            size /= 1024.0
            i += 1

        return f"{size:.2f} {size_names[i]}"

    def get_backup_info(self, filename):
        """获取备份文件信息"""
        backup_dir = self._get_backup_dir()
        filepath = os.path.join(backup_dir, filename)
        if not os.path.exists(filepath):
            return None

        stat = os.stat(filepath)
        return {
            'filename': filename,
            'size': stat.st_size,
            'created_at': datetime.fromtimestamp(stat.st_ctime),
            'formatted_size': self.format_file_size(stat.st_size)
        }